import std.file;
import std.stdio;
import std.exception;
import std.json;
import std.conv;
import std.socket;
import std.socketstream;
import std.stream;
import std.regex;
import std.ctype;
import std.concurrency;

abstract class BEncodeRead{
	byte[] data;	
	int pos=0;
	this(byte[] data){
		this.data=data;
		parse();
		writeln(pos,"=pos   data len=",data.length);
		enforce(pos==data.length);		
	}
	void parse(){
		if( pos>=data.length ){
			return;
		}
		if( data[pos]=='d' ){
			pos++;
			dictStart();
			for(;;){
				if( data[pos]=='e' ){
					pos++;
					dictEnd();
					return;
				}
				parse();
				parse();
			}
		}
		if( data[pos]=='l' ){
			pos++;
			listStart();
			for(;;){
				if( data[pos]=='e' ){
					pos++;
					listEnd();
					return;
				}
				parse();
			}
		}
		if( data[pos]=='i' ){
			pos++;
			intValue(readInt());
			return;
		}
		// byte string
		int len = readLen();
		byte[] str = data[pos..pos+len];
		//writeln("len ",len," str len ",str.length); 
		pos += len;
		byteString(str);
	}
	
	long readInt(){
		long v=0;
		bool empty=true;
		bool minus=false;
		if( data[pos]=='-' ){
			minus=true;
			pos++;
		}		
		for(;;){
			byte b = data[pos];
			pos++;
			if( b>='0' && b<='9' ){
				v = v*10+(b-'0');
				empty=false;
			}else{
				enforce(!empty);
				enforce(b=='e');
				return v;				
			}			
		}
	}
	
	int readLen(){
		int len=0;
		bool empty=true;
		for(;;){
			byte b = data[pos];
			pos++;
			if( b>='0' && b<='9' ){
				len = len*10+(b-'0');
				empty=false;
			}else{
				enforce(!empty);
				enforce(b==':');
				return len;				
			}			
		}
	}
	
	void dictStart();
    void dictEnd();
	void listStart();
	void listEnd();
	void intValue(long v);
	void byteString(byte[] str);
}

class BEncodeLogJson : BEncodeRead {
	this(byte[] data){
		super(data);
	}

	void dictStart(){
		writeln("{");
 	}
    void dictEnd(){
		writeln("}");
    }
	void listStart(){
		writeln("[");
	}
	void listEnd(){
		writeln("]"); 
	}
	void intValue(long v){
		writeln(v,", ");
	}
	void byteString(byte[] str){
		writeln("str=",cast(string)str,",  ");
	}
}

class BEncodeWriter{
	byte[] w;
	void dictStart(){
		w ~= 'd';
	}
    void dictEnd(){
    	w ~= 'e';
    }
	void listStart(){
		w ~= 'l';
	}
	void listEnd(){
		w ~= 'e';
	}
	void intValue(long v){
		w ~= 'i';
		w ~= to!string(v);
		w ~= 'e'; 
	}
	void byteString(string str){
		byteString(cast(byte[])str);
	}
	void byteString(byte[] str){
		w ~= to!string(str.length);
		w ~= ':';
		w ~= str;
	}
}

byte[] mkTorrentFile(string announceURL, string fname, string comment){
    BEncodeWriter w = new BEncodeWriter();
	w.dictStart();
	w.byteString("announce");
	w.byteString(announceURL);
	w.byteString("comment");
	w.byteString(comment);
	w.byteString("creation date");
	w.intValue(1291814341);
	w.byteString("info");
	{
		w.dictStart();
		w.byteString("length");
		w.intValue(torrent_data.length);
		w.byteString("name");
		w.byteString(fname);
		w.byteString("piece length");
		w.intValue(262144);
		w.byteString("pieces");
		
		w.byteString(torrent_data_sha1);
		
		w.byteString("private");
		w.intValue(1);
		w.dictEnd();
	}
	w.dictEnd();
	return w.w;
}

byte[] mkTrackerResponse(){
    enforce(PEER_ID.length==20);
	
	BEncodeWriter w = new BEncodeWriter();
	w.dictStart();
	w.byteString("complete");
	w.intValue(1);
	w.byteString("incomplete");
	w.intValue(0);
	w.byteString("interval");
	w.intValue(60); // seconds
	
	//w.byteString("peers");
	//w.byteString(cast(byte[])[127,0,0,1, 6881/256, 6881%256]);
	
	
	w.byteString("peers");
	{
		w.listStart();
		w.dictStart();
		
//		ip: peer's IP address either IPv6 (hexed) or IPv4 (dotted quad) or DNS name (string)
//		peer id: peer's self-selected ID, as described above for the tracker request (string)
//		port: peer's port number (integer)
		w.byteString("ip");
		w.byteString("192.168.1.145");
		w.byteString("peer id");
		w.byteString(PEER_ID);
		w.byteString("port");
		w.intValue(PEER_PORT);

		w.dictEnd();
		w.listEnd();
	}
	
	w.byteString("tracker id");
	w.byteString("DTRAK123451234512345");
	w.dictEnd();
	return w.w;
}

byte[] torrent_data = cast(byte[])"1234";
byte[] torrent_data_sha1 = cast(byte[])x"7110eda4d09e062aa5e4a390b0a572ac0d2c0220";

ushort PEER_PORT=6885;
enum byte[] PEER_ID = cast(byte[])"DTRAK12342*********M";
static assert( PEER_ID.length==20 );

enum string PROTO_NAME = "BitTorrent protocol";
enum byte PROTO_NAME_LENGTH = PROTO_NAME.length & 0xff;

enum PEER_MSG : byte {choke=0, unchoke=1, interested=2,
	not_interested=3, have=4, bitfield=5, request=6,
	piece=7, cancel=8, port=9};

void peerSocketProcessor(shared Socket clientSh){
	Socket client = cast(Socket)clientSh;
	scope(exit) client.close();
	
	writeln("client peer!!!!!!!!!!!!!!!!!!!!");
	stdout.flush();
	Stream ss = new SocketStream(client); 

	byte pstrlen;
	ss.read(pstrlen);
	writeln("pstr len ",pstrlen," need len ",PROTO_NAME_LENGTH);
	stdout.flush();
	if( pstrlen!=PROTO_NAME_LENGTH ){
		return;		
	}
	char[] pstr = new char[pstrlen];
	ss.readExact(pstr.ptr, pstrlen);
	writeln("pstr len ",pstrlen , " " , pstr);
	stdout.flush();
	if( pstr!=PROTO_NAME ){
		writeln("pstr not PROTO_NAME ",PROTO_NAME);
		stdout.flush();
		return;
	}
	byte[8] reserved;
	ss.readExact(&reserved, reserved.sizeof);
	writeln("reserved ", reserved);
	stdout.flush();
	byte[20] info_hash;
	ss.readExact(info_hash.ptr, info_hash.sizeof);
	writeln("info_hash ", info_hash);
	stdout.flush();
	sendMyHandshake(ss, info_hash);

	byte[20] clientPeerId;
	ss.readExact(clientPeerId.ptr, clientPeerId.sizeof);
	writeln("clientPeerId", cast(char[])clientPeerId);
	stdout.flush();
	
	byte[1] bitField = [0b0000000];
	sendPeerMsg(ss, PEER_MSG.bitfield, bitField);
	
	
	byte[4] piece = uintBigEndian(0);
	sendPeerMsg(ss, PEER_MSG.have, piece);  

    sendPeerMsg(ss, PEER_MSG.unchoke); 
	
	for(;;){
		byte[] peerMsg = readPeerMsg(ss);
		writeln("peerMsg ", peerMsg);
		if( peerMsg[0] == PEER_MSG.request && peerMsg.length==13 ){
			//request: <len=0013><id=6><index><begin><length>
			uint index = bigEndian2uint(peerMsg, 1);
			uint begin = bigEndian2uint(peerMsg, 1+4);
			uint length = bigEndian2uint(peerMsg, 1+4+4);
			writeln("request ", index, " ", begin, " ", length);
			stdout.flush();
			
			//piece: <len=0009+X><id=7><index><begin><block>
			byte[] block = torrent_data;
			uint len = cast(uint)(9 + block.length);
			
			writePeerInt4( ss, len );
			ss.write(cast(byte)PEER_MSG.piece);
			writePeerInt4( ss, index );
			writePeerInt4( ss, begin );
			ss.writeExact( block.ptr, block.length );
			writeln("piece msg written ");
			stdout.flush();
		}
		//byte datab;
		//ss.read(datab);
		//writeln("peer",datab," ",cast(char)datab);
		stdout.flush();
	}
}

void sendPeerMsg(Stream ss, byte code){
	byte[0] no_payload;
	sendPeerMsg(ss, code, no_payload);
}

byte[4] uintBigEndian(uint val){
	byte[4] lenBigEndian = [
		(val >> 24) & 0xff,
		(val >> 16) & 0xff,
		(val >> 8) & 0xff,
		val & 0xff,
	];
	return lenBigEndian;
}

uint bigEndian2uint(byte[] data, int pos){
	uint val = data[pos]<<24 | data[pos+1]<<16 | data[pos+2]<<8 | data[pos+3];
	return val;
}

void writePeerInt4(Stream ss, uint val){
	byte[4] bigEndian = uintBigEndian(val);
	ss.writeExact( bigEndian.ptr, bigEndian.length );
}

void sendPeerMsg(Stream ss, byte code, byte[] payload){
	uint len = cast(uint)(1 + payload.length);
	writePeerInt4(ss, len);
	ss.write(code);
	ss.writeExact(payload.ptr, payload.length);
}

byte[] readPeerMsg(Stream ss){
	byte[4] lenBigEndian;
	ss.readExact(lenBigEndian.ptr, lenBigEndian.length);
	uint len = bigEndian2uint( lenBigEndian, 0 );
	writeln("peer msg len ", len);
	stdout.flush();
	byte[] data = new byte[len];
	ss.readExact(data.ptr, len);
	return data;	 
}

void sendMyHandshake(Stream ss, byte[20] info_hash){
	ss.write(PROTO_NAME_LENGTH);
	ss.writeString(PROTO_NAME);
	ubyte[8] reserved;
	ss.writeExact(reserved.ptr, reserved.length);
	ss.writeExact(info_hash.ptr, info_hash.length);
	ss.writeExact(PEER_ID.ptr, PEER_ID.length);
} 

void serverPeer(){
	writeln("listening peer ",PEER_PORT);
	stdout.flush();
	Socket sock = new TcpSocket();
	sock.bind(new InternetAddress(InternetAddress.ADDR_ANY, PEER_PORT));
	sock.listen(100);
	for(;;){
	  shared Socket client = cast(shared Socket)sock.accept();
	  spawn( &peerSocketProcessor, client );
    }
	
}

void trackerSocketProcessor(size_t clientA){
	Socket client = *(cast(Socket*)clientA);
	writeln("client");
	stdout.flush();
	Stream ss = new SocketStream(client);
	foreach(char[] line; ss) {
	   writeln("line: ",line);
	   stdout.flush();
	   writeln("11");
       stdout.flush();
	   
	   //if( line=="" ){
	   //	  break;
	   //}
	   string[string] getProps = parseHttpGet(to!string(line));
	  writeln("client request ",getProps);
      stdout.flush();
	   if( getProps.length>0 ){
	   	  byte[] resp = mkTrackerResponse();
	   	  char[] respHead = cast(char[])"HTTP/1.1 200 Ok\r\nContent-Type: text/plain\r\nContent-Length: "~to!string(resp.length)
	 ~"\r\n\r\n";
	  	writeln("responding");
		stdout.flush();
	   	  ss.write(cast(ubyte[])respHead);
	   	  ss.write(cast(ubyte[])resp);
	   	  writeln("response written",resp);
			stdout.flush();
	   	  ss.flush();
	   }
       stdout.flush();
	 }
	writeln("client end");
	stdout.flush();
}	

void serverTracker(){
	writeln("listening");
	stdout.flush();
	Socket sock = new TcpSocket();
	sock.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1);
	sock.bind(new InternetAddress(InternetAddress.ADDR_ANY, 6969));
	sock.listen(100);

    for(;;){
		Socket client = sock.accept();
		spawn(&trackerSocketProcessor, cast(size_t)&client);
    }
/+

line: GET /announce?info_hash=%CB%B3FwT%10%3A%0A%AC%9FT%ECEwT%DF%DB%7D%C8Y&peer_id=-TR1920-c4rsol7r7hse&port=51413&uploaded=0&downloaded=0&left=1&numwant=200&key=0zrr7y2h&compact=1&supportcrypto=1&event=started HTTP/1.1
line: User-Agent: Transmission/1.92
line: Host: localhost:6969
line: Accept: */*
line: Accept-Encoding: gzip;q=1.0, deflate, identity
line: 

+/    
}

int hexParse(char c){
	if( c>='0' && c<='9' ){
		return c - '0';
	}
	if( c>='A' && c<='F' ){
		return c - 'A';
	}
	if( c>='a' && c<='f' ){
		return c - 'a';
	}
	assert(false,"hex c = " ~ c);
}

string urlDecode(string urlEncoded){
	//writeln("url decode ", urlEncoded);
	string ret;
	int pos=0;
	for(;;){
		if( pos>=urlEncoded.length ){
			return ret;
		}
		if( urlEncoded[pos]=='%' ){
			int ccode = hexParse(urlEncoded[pos+1])*16 + hexParse(urlEncoded[pos+2]);
			//writeln(pos, "%", ccode);
			ret ~= ccode;
			pos += 3;
		}else{
			//writeln(pos);
			ret ~= urlEncoded[pos];
			pos++;
		}
	}
}

string[string] parseHttpGet(string s){
	string[string] ret;
	//writeln(1);
	foreach(m; match(s, regex(r"^GET /a[^?]*\?(.+) HTTP/1.1$"))){
	        //writefln("pre hit post %s[%s]%s", m.pre, m.hit, m.post);
	        //writeln("captures ", m.captures[1]);
	        auto pairs = splitter(m.captures[1], regex("&"));
	        //writeln("pairs ",pairs);
	        foreach(p; pairs){
	        	//writeln("p ",p);
	        	Splitter!string ab = splitter(p, regex("="));
	        	string key = ab.front;
	        	//writeln("key ",key);
	        	ab.popFront();
	        	string val = ab.front;
	        	//writeln("val ",val);
	        	ret[key] = urlDecode(val);
	        }
    }
	//writeln("ret size ",ret.length);
	writeln(ret);
	return ret;
}

void main(){
	string fname = "/opt3/denny/Downloads/otp_src_R14B01.tar.gz.torrent";
	byte[] fdata = cast(byte[])read(fname);
	writeln("len",fdata.length);
	//new BEncodeLogJson(fdata);
	writeln("*************");
	//byte[] newTorrent = mkTorrentFile("http://localhost:6969/announce","--comment--");
byte[] newTorrent = mkTorrentFile("http://localhost:6969/a",
		"fileLen4.txt","--comment--");
	std.file.write("/tmp/4.torrent",newTorrent);
	new BEncodeLogJson(newTorrent);
	
    spawn( &serverPeer );
	serverTracker();
	//parseHttpGet("GET /announce?info_hash=%CB%B3FwT%10%3A%0A%AC%9FT%ECEwT%DF%DB%7D%C8Y&peer_id=-TR1920-c4rsol7r7hse&port=51413&uploaded=0&downloaded=0&left=1&numwant=200&key=0zrr7y2h&compact=1&supportcrypto=1&event=started HTTP/1.1");
}